---
title: Configurable Widgets
description: Create configurable Widgets and connect them to your Flutter App
---

# Configurable Widgets

Sometimes you want to give your users the ability to configure their HomeScreenWidgets to their liking.
For example a weather widget that can show the weather for different locations or a todo list that can show different lists.

## iOS

On iOS there are two ways to make your App Configurable.
The new and recommended way by Apple is to use [WidgetConfigurationIntent](#widgetconfigurationintent)
you can also provide backwards compatibility using [SiriKit Intents](#sirikit-intents)

### WidgetConfigurationIntent

<Steps>
    <Step title="Create Widget Extension">
        If you create a new Widget add an extension by going <kbd>File</kbd> > <kbd>New</kbd> > <kbd>Target</kbd> > <kbd>Widget Extension</kbd>
        This time checking the box for <kbd>Include Configuration App Intent</kbd>
        ![Setting up the Extension with Configuration Intent](/assets/configurable/ios/create-widget-extension-ios-with-configuration.webp)

        The generated Widget code includes the following classes:
        - `AppIntentTimelineProvider` - Provides a Timeline of entries at which the System will update the Widget automatically using an AppIntent for configuration
        - `TimelineEntry` - Represents the Data Object used to build the Widget. The `date` field is necessary and defines the point in time at which the Timeline would update
        - `View` - The Widget itself, which is built with SwiftUI
        - `Widget` - Configuration: Make note of the `kind` you set in the Configuration as this is what's needed to update the Widget from Flutter.

        In a separate file there is a `WidgetConfigurationIntent` defined that will determine the configuration options of the Widget in `AppIntent.swift`.

    </Step>

    <Step title="Adjusting the Configuration Intent">

        You can add fields that appear in the configuration by changing the generated `ConfigurationAppIntent` class.

        ```swift
        @Parameter(title: "Name", default: "World")
        var name: String
        ```

        You can access the fields of the configuration when building the Widget UI

        ```swift
        struct ConfigurableWidgetEntryView : View {
            var entry: Provider.Entry

            var body: some View {
                VStack {
                    Text("Hello")
                    Text(entry.configuration.name)
                }
            }
        }
        ```

        <div align="center" class="justify-center flex" style={{ gap: '8px', alignItems: 'center' }}>
            <Image src="/assets/configurable/ios/widget-with-name-configuration.webp"/>
            <Video src="/assets/configurable/ios/world-to-flutter.mp4"/>
        </div>

    </Step>

    <Step title="Getting Data from Flutter to the Configuration">

        #### App Groups
        In order for sending Data from your Flutter App to the Widget we need to use [App Groups](https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.security.application-groups).
        <Info>App Groups require a paid Apple Developer Account</Info>

        ##### Create a new App Group
        Visit the [Apple Developer Portal](https://developer.apple.com/account/resources/identifiers/applicationGroup/add/) and create a new App Group.
        
        ##### Enable App Groups in XCode
        Add the App Group capability to <b>both the App Target (Runner) and your Widget Extension</b> in XCode.
        ![Enable App Groups in XCode](/assets/configurable/ios/app-group.webp)

        ##### Register App Group in Flutter
        In your Flutter code register the App Group with `home_widget`

        ```dart
        import 'package:home_widget/home_widget.dart';
        void main() {
            WidgetFlutterBinding.ensureInitialized();
            HomeWidget.setAppGroupId('group.YOUR_APP_GROUP_ID');
            runApp(MyApp());
        }
        ```

        #### Send Data

        You can store data from Flutter for the Widget using `HomeWidget.saveWidgetData` to use it as options in the configuration panel.

        ```dart
        final punctuations = [
            '!',
            '!!!',
            '.',
            '?',
            // Wave Emoji
            '\u{1F44B}',
        ];
        await HomeWidget.saveWidgetData(
            'punctuations',
            jsonEncode(punctuations),
        );
        ```

        #### Use Values from Flutter as Options in Customize Panel

        In XCode and Swift add an `AppEntity` and matching `EntityQuery`


        ```swift

        @Parameter(title: "Punctuation")
        var punctuation: PunctuationEntity
        }

        // Make Entity Codable so home_widget
        // That way home_widget can best extract the values from a configuration
        struct PunctuationEntity: AppEntity, Codable {

        let id: String

        static var typeDisplayRepresentation: TypeDisplayRepresentation = "Punctuation"
        static var defaultQuery = PunctuationQuery()

        var displayRepresentation: DisplayRepresentation {
            DisplayRepresentation(title: "\(id)")
        }
        }

        struct PunctuationQuery: EntityQuery {

        func punctuations() -> [PunctuationEntity] {
            let userDefaults = UserDefaults(suiteName: "YOUR_APP_GROUP")

            do {
            let jsonPunctuations = (userDefaults?.string(forKey: "punctuations") ?? "[\"!\"]").data(
                using: .utf8)!
            let stringArray = try JSONDecoder().decode([String].self, from: jsonPunctuations)
            return stringArray.map { punctuation in
                PunctuationEntity(id: punctuation)

            }
            } catch {
            return [PunctuationEntity(id: "!")]
            }

        }

        func entities(for identifiers: [PunctuationEntity.ID]) async throws -> [PunctuationEntity] {
            let results = punctuations().filter { identifiers.contains($0.id) }
            return results
        }

        func suggestedEntities() async throws -> [PunctuationEntity] {
            return punctuations()
        }

        func defaultResult() async -> PunctuationEntity? {
            try? await suggestedEntities().first
        }

        }
        ```

        Now you can access this new parameter in your Widget Code

        ```swift
        struct ConfigurableWidgetEntryView: View {
        var entry: Provider.Entry

        var body: some View {
            VStack {
            Text("Hello")
            Text(entry.configuration.name)
            Text(entry.configuration.punctuation.id) // [!code highlight]
            }
        }
        }
        ```
    </Step>

    <Step title="Seeing Configuration in Flutter">

        You need to let home_widget know about the relation of the Intent and the `kind` (what you need for updating the widget).

        ##### Add `Runner` as a Target of the Widget

        Open the Intent Swift File and in the Details Pane enable the `Target Membership` for the `Runner` Target

        ![Enable Target Membership for Runner](/assets/configurable/ios/app-intent-target-membership.webp)

        ##### Register Configuration in AppDelegate

        In your `AppDelegate.swift` register the Configuration Intent together with the `kind` of your Widget

        ```swift
        import Flutter
        import UIKit
        import home_widget // [!code ++]

        @main
        @objc class AppDelegate: FlutterAppDelegate {
        override func application(
            _ application: UIApplication,
            didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
        ) -> Bool {
            GeneratedPluginRegistrant.register(with: self)
            if #available(iOS 17.0, *) { // [!code ++]
            HomeWidgetPlugin.setConfigurationLookup(to: [ // [!code ++]
                "ConfigurableWidget": ConfigurationAppIntent.self // [!code ++]
            ]) // [!code ++]
            } // [!code ++]
            return super.application(application, didFinishLaunchingWithOptions: launchOptions)
        }
        }

        ```

        To get the configuration a user made to a Widget you can use `HomeWidget.getInstalledWidgets()`.

        ```dart
        final configuration = await HomeWidget.getInstalledWidgets();
        ```

        this will give you a list of `HomeWidgetInfo` in there there is a `Map<String, dynamic>? configuration`.

        <Warning>
            On iOS Simulators `HomeWidget.getInstalledWidgets()` will always return an empty list.

            To fully test this you should test on a real iOS Device
        </Warning>

        For our Greeting Example the Configuration would be:

        ```json
        {
            "name": "Documentation",
            "punctuation": {
                "id": "!!!"
            }
        }
        ```

        <div align="center" class="justify-center flex" style={{ gap: '8px', alignItems: 'center' }}>
            <Image src="/assets/configurable/ios/configured-widgets.webp"/>
            <Image src="/assets/configurable/ios/configuration-in-flutter.webp"/>
        </div>
    </Step>
</Steps>

### SiriKit Intents

Before iOS 17 SiriKit Intents where used to build configurable widgets. You can use this to add backwards compatibility.

<Info>
    You could also use SiriKit Intents standalone however it is unclear how long Apple will support this.
</Info>

#### Basic setup

<Steps>
    <Step title="Create Intent">
        Create a new SiriKit Intent File by going to <kbd>File</kbd> > <kbd>New</kbd> > <kbd>SiriKit Intent File</kbd>

        <Image src="/assets/configurable/ios/siri-intent-file.webp"/>

        Create a new Intent using the <kbd>+</kbd> button in the bottom left.

        In the Intent set <b>Category</b> to `View` and enable `Intent is eligible for widgets`

        <Image src="/assets/configurable/ios/intent-configuration.webp"/>

        Add a simple parameter to the Intent. FOr our example a `String` parameter called `name`

        <Image src="/assets/configurable/ios/simple-parameter.webp"/>
    </Step>

    <Step title="Using Intent in Widget">
    To use our custom generated Intent in the generated Widget we need to to adjust the automatically generated code to conform to the correct APIs.

    Adjust your `SimpleEntry` to use properties instead of the Intent so we can use both the AppIntent and the SiriIntent.
    ```swift
    struct SimpleEntry: TimelineEntry {
        let date: Date
        let configuration: ConfigurationAppIntent // [!code --]
        let name: String // [!code ++]
        var punctuation: String? = nil // [!code ++]
    }    
    ```

    Create an `IntentTimelineProvider`

    ```swift
    struct IntentProvider: IntentTimelineProvider {
        typealias Entry = SimpleEntry

        typealias Intent = GreetingIntentIntent

        func placeholder(in context: Context) -> SimpleEntry {
            SimpleEntry(date: Date(), name: "World")
        }

        func getSnapshot(
            for configuration: GreetingIntentIntent, in context: Context,
            completion: @escaping (SimpleEntry) -> Void
        ) {
            completion(SimpleEntry(date: Date(), name: configuration.Name))
        }

        func getTimeline(
            for configuration: GreetingIntentIntent, in context: Context,
            completion: @escaping (Timeline<SimpleEntry>) -> Void
        ) {
            var entries: [SimpleEntry] = []

            // Generate a timeline consisting of five entries an hour apart, starting from the current date.
            let currentDate = Date()
            for hourOffset in 0..<5 {
            let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
            let entry = SimpleEntry(date: entryDate, name: configuration.Name)
            entries.append(entry)
            }

            completion(Timeline(entries: entries, policy: .atEnd))
        }
    }
    ```

    In the Widget definition add code to use the appropriate configuration provider.

    ```swift
    var body: some WidgetConfiguration {
        if #available(iOS 17.0, *) { // [!code ++]
        AppIntentConfiguration( // [!code --]
            return AppIntentConfiguration( // [!code ++]
                kind: kind, intent: ConfigurationAppIntent.self, provider: Provider()
            ) {
                entry in
                ConfigurableWidgetEntryView(entry: entry)
                .containerBackground(.fill.tertiary, for: .widget)
            }
        } else { // [!code ++]
            return IntentConfiguration( // [!code ++]
                kind: kind, // [!code ++]
                intent: GreetingIntentIntent.self, // [!code ++]
                provider: IntentProvider() // [!code ++]
            ) { entry in // [!code ++]
                ConfigurableWidgetEntryView(entry: entry) // [!code ++]
            } // [!code ++]
        } // [!code ++]
    }
    ```
    </Step>

    <Step title="Getting Data from Flutter to the Configuration">
        With SiriKit Intents you need to add a new `IntentHandler` that can handle using Data you send from home_widget.

        For this add a new `Intents Extension` to your App. Using <kbd>File</kbd> > <kbd>New</kbd> > <kbd>Target</kbd> > <kbd>Intents Extension</kbd>

        <Image src="/assets/configurable/ios/siri-intents-extension.webp"/>

        In your SiriKit Intents definition add the newly created Extension as a Target
        
        <Image src="/assets/configurable/ios/siri-extension-target-membership.webp"/>

        In the same configurations file add a new Type and configure it to the options you need.

        <Image src="/assets/configurable/ios/siri-new-type.webp"/>

        <Image src="/assets/configurable/ios/siri-created-type.webp"/>

        Create a Field that uses this new type. Ensuring to enable <i>Options are provided dynamically</i>

        <Image src="/assets/configurable/ios/siri-select-type.webp"/>
        
        <Image src="/assets/configurable/ios/siri-extension-dynamic-option.webp"/>

        Implement the `IntentHandler` to handle the new Intent

        ```swift
        import Intents

        class IntentHandler: INExtension, GreetingIntentIntentHandling {
        
        func providePunctuationOptionsCollection(for intent: GreetingIntentIntent) async throws -> INObjectCollection<Punctuation> {
            let userDefaults = UserDefaults(suiteName: "YOUR_APP_GROUP")
            
            do {
                let jsonPunctuations = (userDefaults?.string(forKey: "punctuations") ?? "[\"!\"]").data(using: .utf8)!
                let stringArray = try JSONDecoder().decode([String].self, from: jsonPunctuations)
                let items = stringArray.map { punctuation in
                    Punctuation(identifier: punctuation, display: punctuation)
                }
                return INObjectCollection(items: items)
                
            } catch {
                return INObjectCollection(items: [Punctuation(identifier: "!", display: "!")])
            }
        }
        }
        ```

    </Step>
    <Step title="Seeing Configuration in Flutter">
    Similar to the `WidgetConfigurationIntent` you can use `HomeWidget.getInstalledWidgets()` to get the configuration of the Widget.

    ```dart
    final configuration = await HomeWidget.getInstalledWidgets();
    ```

    this will give you a list of `HomeWidgetInfo` in there there is a `Map<String, dynamic>? configuration`.


    For our Greeting Example the Configuration would be:

    ```json
    {
        "Name": "Siri",
        "Punctuation": {
            "identifier": "👋",
            "displayString": "👋"
        }
    }
    ```

    <div align="center" class="justify-center flex" style={{ gap: '8px', alignItems: 'center' }}>
            <Image src="/assets/configurable/ios/siri-configured-example.webp"/>
            <Image src="/assets/configurable/ios/siri-configuration-in-flutter.webp"/>
        </div>
    
    </Step>
</Steps>
